The following code samples represent some of the tasks in N4 that you can accomplish through Groovy.
Here is a slightly more complex example that deals with a use case we found at PNC (Pusan, Korea).
There is a requirement that a change of discharge port or change of vessel is billable only if the container is moved in the yard subsequent to the change of port or vessel. If both are changed, then only the change of vessel is billed.
To configure this one we first define to new billable Event Types, "CHANGE_VSL" and "CHANGE_POD". Then we create a Notice Request which reacts to Yard Moves with this Groovy code:
// See if the Unit was previously rerouted
GroovyEvent rerouteEvent = event.getMostRecentEvent("REROUTE_CTR");
if (rerouteEvent != null) {
// If outbound carrier was changed, record CHANGE_VSL event
if (rerouteEvent.wasFieldChanged("OutboundCarrierId")) {
if (event.getMostRecentEvent("CHANGE_VSL") == null) {
event.postNewEvent("CHANGE_VSL");
}
// If discharge port was changed, record CHANGE_POD event
} else if (rerouteEvent.wasFieldChanged("POD")) {
if (event.getMostRecentEvent("CHANGE_POD") == null
&& event.getMostRecentEvent("CHANGE_VSL") == null) {
event.postNewEvent("CHANGE_POD");
}
}
}
Example: Post new Event depending on a Query
This example, invoked on a discharge event, determines if there are any more import units left on the vessel from which the current unit was discharged. If no more are on board, then the "LAST_OFF" event is recorded for this unit.
// create a query instance
def query = api.createQuery("Unit")
// specify the 'where' clause
def vesselId = event.getProperty("InboundCarrierId")
query.addEqPredicate("InboundCarrierId", vesselId)
query.addEqPredicate("Category", "I")
query.addEqPredicate("PositionLocId", vesselId)
// count the number of matches
// (i.e. number of Units remaining on-board.)
def count = query.count()
if (count == 0) {
event.postNewEvent("LAST_OFF")
}
Here we create and execute a GroovyQuery. The method api.createQuery() is the factory for GroovyQuery's. GroovyQuery has a number of features, all explained in the JavaDoc.
Example: Send message to an outside system
In the groovy support for General Notices, api.sendXml is used to send an xml message via Java Messaging Service (../../../../../../../display/sn4o/event+processing+via+jms+for+n4+2.1+and+prior+releases). api.sendJMSBytesMessage is used for sending UTF-8 based byte messages.
To call api.sendXML(), you must first configure the destination url on the Facility from the Integration Services view, and reload the the configurations from the Actions menu of the Integration Configurations view.
event.getPropertyXml API will build a string xml given a list of reportable entity tags. The below example sends attributes of a unit.
def list = ["UnitNbr", "Category", "FreightKind", "RequiredTempC", "InTime"]
def xml = event.getPropertyXml("unit", list);
// send the XML to the jms queue specified on the Facility of the current user scope.
api.sendXml(xml)
// also write the message to the system log
api.log(xml)
Execution of the following code will send the following xml message to the facility configured JMS queue.
<unit EVENT_ID="TEST EVENT" EVENT_GKEY="321201" ENTITY_GKEY="12523"
UnitNbr="CRLU3160549"
Category="Import" FreightKind="FCL" RequiredTempC="4.0"
InTime="2006-08-25 05:24" />
The time format is output as configured in the DATE_TIME_DISPLAY_FORMAT settings.
The next example shows sending vessel visit updates via JMS.
Example: Vessel Updates Via JMS
To synchronize the vessel visit schedule from N4 with your external systems via JMS, you can call the following groovy code from the general notice pegged to the VV_UPDATE event which will be called for both the create and the update of a vessel visit.
println("BEGIN JMS Message" );
def list = ["VesselVisitId",
"VesselRadioCallSign",
"VesselId","VesselName",
"LastBerthingQuayId", "LastBerthingQuayName",
"VesselLineOperator","VesselLineOperatorName",
"VesselClassLoaCm", "VesselClassGrossRegTon", "VesselClassNetRegTon",
"IbVoyageNbr", "ObVoyageNbr",
"TimeEstimatedArrival", "TimeEstimatedDeparture",
"TimeActualArrival", "TimeActualDeparture",
"VesselService","VesselServiceName",
"TimeCargoCutoff", // dry cutoff
"TimeStartWork", "TimeEndWork",
"VisitPhase"]
def xml = event.getPropertyXml("vesselvisit", list);
println(xml);
api.sendXml(xml);
println("END JMS Test");
The getPropertyXml API
public String getPropertyXml(String inElementName, List<String> inReportingTags)
will construct xml list of attributes. Also api.sendJMSBytesMessage(xml) API should be used in lieu of a text message for .Net JMS clients using NMS.
The log output will look something like this which lists the sent JMS message
BEGIN JMS Message
<vesselvisit EVENT_ID="UPDATE_VV" EVENT_GKEY="323381" ENTITY_GKEY="13582"
VesselVisitId="PRV053A" VesselRadioCallSign="ELJP4"
VesselId="PRV" VesselName="PROVIDER" LastBerthingQuayId="C3" LastBerthingQuayName="??C3??"
VesselLineOperator="NYK" VesselLineOperatorName="Nippon Yusen Kaisha (NYK)"
VesselClassLoaCm="123456789" VesselClassGrossRegTon="33333.0" VesselClassNetRegTon="22222.0"
IbVoyageNbr="157" ObVoyageNbr="158"
TimeEstimatedArrival="2006-05-17 02:00" TimeEstimatedDeparture="2006-05-17 18:00"
TimeActualArrival="2006-08-25 04:48" TimeActualDeparture="" VesselService="NZAX"
VesselServiceName="NZAX" TimeCargoCutoff=""
TimeStartWork="2006-08-25 05:24" TimeEndWork=""
VisitPhase="40WORKING" />
END JMS Test
The vessel visit phase is returned as the database stored code instead of the description:
<codes entity="VesselVisitDetails" tag="VisitPhase">
<code dbvalue="10CREATED" desc="Created" />
<code dbvalue="20INBOUND" desc="Inbound" />
<code dbvalue="30ARRIVED" desc="Arrived" />
<code dbvalue="40WORKING" desc="Working" />
<code dbvalue="50COMPLETE" desc="Complete" />
<code dbvalue="60DEPARTED" desc="Departed" />
<code dbvalue="70CLOSED" desc="Closed" />
<code dbvalue="80CANCELED" desc="Canceled" />
<code dbvalue="90ARCHIVED" desc="Archived" />
</codes>
Note when you advance the phase visit of a vessel, both the VV_PHASE as well as the VV_UPDATE event will both be triggered. This will be desired behavior as you may have different notification behavior configured for both events.
No delete events will be sent since inside the notification cannot send an event on an object that no longer exists. You are recommended to purge both systems for inactive visits for an inactivce date horizon.
For on demand synchronization, SN4 provides Universal Query via SNX mode.
Example: Add Optional Properties to the JMS Message Header
String xml = "<xml>...</xml>";
Map properties = new HashMap();
properties.put("LineOperator", "MATSON");
api.sendXml(xml, properties);
Other available groovy api methods also take the additional header properties.
GroovyAPI.sendXml(String inXml, Map inProperties)
GroovyAPI.sendXml(String inIntegrationService, String inXml, Map inProperties)
GroovyAPI.sendXml(sendJMSBytesMessage(String inXml, Map inProperties)
GroovyAPI.sendJMSBytesMessage(String inIntegrationService, String inXml, Map inProperties)
Example: Writing XML message to a local file using Groovy
Here we show Groovy code that sends an XML-formatted message to a local file in response to an event.
The file will be created in C:\Navis\tomcat folder if specific folder name is not mentioned.
// To get train visit details
def trainvisit = event.getEntity();
def fileName = "TRAINPHASE"+System.currentTimeMillis() + ".txt"
api.log("**TRAINVISIT**")
def railCarId =trainvisit.getFieldValue("rvdtlsId");
def railOperator =trainvisit.getRvdtlsRR().getBzuId();
def visitPhase = trainvisit.getCvdCv().getCvVisitPhase().getName();
def facilityId = "BMUT";
visitPhase = visitPhase.substring(2);
def writer = new StringWriter();
def builder = new groovy.xml.MarkupBuilder( writer);
def trainvisitnew = builder.trainvisitnew( facilityID:facilityId, id:railCarId, visitphasecomment:visitPhase, rrId:railOperator);
def xml = writer.toString();
// also write the message to the local file with fileName defined earlier.
new File(fileName).write(xml)
// also write the message to the system log
api.log(xml)
Example: Maintaining Hazardous Information
You can ensure that a Unit is marked as hazardous with this Groovy code associated with an Event:
def unit = event.getEntity();
def hazardItem = unit.getUnitGoods().attachHazard("3", "1203");
In the above, the first property, "3", is the IMDG class. The second property, "1203", is the UN Number.
The method attachHazard() method ensures that a HazardItem exists for the Unit with the given IMDG class and UN Number. It returns this HazardItem to the caller.
You can then set any HazardItem property with a call to that property's setter. For example, to set the page number:
hazardItem.setHzrdiPageNumber("52");
Below are the simple properties of a HazardItem that you can set in this way. (These properties map one-to-one to the hazard element in EDIFACT, e.g. BAPLIE.)
java.lang.String hzrdiUNnum;
java.lang.Boolean hzrdiLtdQty;
java.lang.String hzrdiPackageType;
java.lang.String hzrdiInhalationZone;
java.lang.String hzrdiExplosiveClass;
java.lang.String hzrdiPageNumber;
java.lang.Double hzrdiFlashPoint;
java.lang.String hzrdiTechName;
java.lang.String hzrdiProperName;
java.lang.String hzrdiEMSNumber;
java.lang.String hzrdiERGNumber;
java.lang.String hzrdiMFAG;
java.lang.String hzrdiHazIdUpper;
java.lang.String hzrdiSubstanceLower;
java.lang.Double hzrdiWeight;
java.lang.String hzrdiPlannerRef;
java.lang.Long hzrdiQuantity;
java.lang.String hzrdiMoveMethod;
java.lang.String hzrdiDeckRestrictions;
java.lang.Boolean hzrdiMarinePollutants;
java.lang.String hzrdiDcLgRef;
java.lang.String hzrdiEmergencyTelephone;
java.lang.String hzrdiNotes;
If you set the "hzrdiQuantity" property you must also set the "hzrdiPackageType" property.
Example: Update the BL Number to match the Booking Number (a MATSON requirement)
def unit = event.getEntity();
def bookingNbr;
def ue = unit.getUnitPrimaryUe();
if (ue.getUeDepartureOrderItem() != null) {
bookingNbr = ue.getUeDepartureOrderItem().getEqboiOrder().getEqboNbr();
}
unit.getUnitGoods().setGdsBlNbr(bookingNbr);
Example: Invoke another Groovy class stored in the "Groovy Plug-ins" repository
Here is a simple example of a Groovy Event that delegates to a class that has been uploaded to the general "Groovy Plug-Ins" repository.
Step 1: Upload this class via the UI in Adminstration>System>Groovy Plug-Ins:
public class MyGroovy {
public void doIt() {
println("Hello World")
}
}
Step 2: Create a Groovy Event handler as follows:
def myGroovy = api.getGroovyClassInstance("MyGroovy");
myGroovy.doIt();
The first line of code above uses a new API, "getGroovyClassInstance()". This API obtains an instance of a Groovy Class that you have previously uploaded.
The second line of code invokes the "doIt()" method of this class.